﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using SuperRyoninRPG.Controller;
using SuperRyoninRPG.Display;
using SuperRyoninRPG.Main;

namespace SuperRyoninRPG.Characters
{
	public abstract class Thing
	{
		protected int FrameWidth;
		public bool IsAlive { get; protected set; }

		/// <summary>
		/// 出現済み
		/// </summary>
		public bool Appearance { get; set; }

		public bool ShowingComment { get; set; }

		public bool Commented { get; set; }

		public Comment Comment { get; set; }

		public Comment[] Talks { get; set; }

		public bool CanTalk
		{
			get { return this.Talks != null; }
		}

		public abstract Animation Animation { get; }
		private readonly Stage stage;

		// アニメーション

		// 水平移動を制御する定数
		protected float Acceleration = 750f;
		protected float AirDragFactor = 0.48f;
		protected float Deceleration = 375f;

		/// <summary>
		///     現在のユーザーの動きの入力。
		/// </summary>
		protected FaceDirection Direction;

		protected float GravityAcceleration = 3400.0f;

		protected float GroundDragFactor = 0.48f;
		protected bool IsDushing;

		public bool IsCrouching { get; set; }

		// ジャンプして
		// いる状態
		protected bool IsJumping;
		protected float JumpControlPower = 0.07f;
		protected float JumpLaunchVelocity = -3500.0f;
		protected float MaxAcceleration = 13000.0f;
		protected float MaxDushAcceleration = 26000.0f;
		protected float MaxFallSpeed = 550.0f;
		protected float MaxJumpTime = 0.65f;

		/// <summary>
		///     現在のユーザーの動きの入力。
		/// </summary>
		protected float Movement;

		protected float StopTime = 0;

		private SpriteEffects flip = SpriteEffects.None;
		private bool isOnGround;

		protected float jumpTime;

		protected float MoveAcceleration;

		//点滅処理
		protected bool Switch;
		private bool previousSwich ;

		private float previousBottom;
		private int previousLeft;
		private int previousRight;

		private int previousTop;

		protected AnimationPlayer Sprite;
		private Vector2 velocity;
		private bool wasJumping;

		protected Vector2 Shift;

		/// <summary>
		///     新しいプレイヤーを構築します。
		/// </summary>
		protected Thing(Stage stage, Vector2 position, int frameWidth)
		{
			this.FrameWidth = frameWidth;
			this.stage = stage;
			this.Sprite = new AnimationPlayer();

			this.Initialize();
		}

		/// <summary>
		///     新しいプレイヤーを構築します。
		/// </summary>
		protected Thing(Stage stage, Vector2 position, int frameWidth,Comment comment,Comment[] talks = null)
			:this(stage, position, frameWidth)
		{
			this.Comment = comment;
			this.Talks = talks;
		}

		public Rectangle LocalBounds
		{
			get
			{
				// テクスチャー サイズ内の境界を計算します。
				//int width = (int)(idleAnimation.FrameWidth * 0.4);
				int width = this.Animation.FrameWidth;
				int left = (this.Animation.FrameWidth - width);
				//int left = 10;

				//int height = (int)(idleAnimation.FrameWidth * 0.8);
				int height = this.Animation.FrameHeight;
				int top = this.Animation.FrameHeight - height;
				return new Rectangle(left, top, width, height);
			}
		}

		public bool AllowTurnImage { get; set; }

		public Stage Stage
		{
			get { return this.stage; }
		}

		public Vector2 Position { get; set; }

		public Vector2 Velocity
		{
			get { return this.velocity; }
			set { this.velocity = value; }
		}

		/// <summary>
		///     プレイヤーの足が地面に着いているかどうかを取得します。
		/// </summary>
		public bool IsOnGround
		{
			get { return this.isOnGround; }
		}

		/// <summary>
		///     このプレイヤーをワールド空間内に拘束する矩形を取得します。
		/// </summary>
		public Rectangle BoundingRectangle
		{
			get
			{
				int left = (int)Math.Round(this.Position.X - this.Sprite.Origin.X) + this.LocalBounds.X;
				int top = (int)Math.Round(this.Position.Y - this.Sprite.Origin.Y) + this.LocalBounds.Y;
				//int left = (int)Math.Round(this.Position.X);
				//int top = (int)Math.Round(this.Position.Y );

				return new Rectangle(left, top, this.LocalBounds.Width, this.LocalBounds.Height);
			}
		}
		private void Initialize()
		{
			this.AllowTurnImage = true;
		}


		/// <summary>
		///     入力を処理し、物理処理を実行して、プレイヤー スプライトを
		/// </summary>
		/// <remarks>
		///     すべての入力ステートが 1 フレームごとにポーリングされるよう該当するステート変数を渡します。
		///     加速度センサーの計測値をゲームに正しく反映するためにはゲームの向きである DisplayOrientation も渡す必要があります。
		///     具体的には向きが LandscapeRight に設定されている場合は加速度センサーの値を反転する必要があります。
		/// </remarks>
		public void Update(
			GameTime gameTime,
			KeyboardState keyboardState,
			GamePadState gamePadState,
			TouchCollection touchState,
			AccelerometerState accelState,
			DisplayOrientation orientation)
		{
			if (!Appearance)
			{
				return;
			}

			this.GetInput(gameTime, keyboardState, gamePadState, touchState, accelState, orientation);

			this.ApplyPhysics(gameTime);

			OnUpdate(gameTime);
		
			// 入力を作成します。
			this.Movement = 0.0f;
			this.IsJumping = false;
		}

		protected virtual void OnUpdate(GameTime gameTime)
		{
			if (this.Updated != null)
			{
				this.Updated(this,new EventArgs());
			}
		}

		public event EventHandler Updated;
		

		/// <summary>
		///     プレイヤーの水平移動とジャンプ コマンドを入力から取得します。
		/// </summary>
		protected abstract void GetInput(
			GameTime gameTime,
			KeyboardState keyboardState,
			GamePadState gamePadState,
			TouchCollection touchState,
			AccelerometerState accelState,
			DisplayOrientation orientation);

		/// <summary>
		///     入力、重力などに基づくプレイヤーの速度と位置を更新します。
		/// </summary>
		public void ApplyPhysics(GameTime gameTime)
		{
			if (!Appearance)
			{
				return;
			}
			if (this.StopTime > 0)
			{
				this.StopTime -= (float) gameTime.ElapsedGameTime.TotalSeconds;
				return;
			}

			var elapsed = (float) gameTime.ElapsedGameTime.TotalSeconds;

			Vector2 previousPosition = this.Position;

			float maxAcceleration = this.IsDushing && this.isOnGround
				? this.MaxDushAcceleration
				: this.MaxAcceleration;

			// 基本速度は、水平移動の制御と重力による下方への加速度の
			// 組み合わせです。
			if (this.Movement > 0)
			{
				if (this.MoveAcceleration < maxAcceleration)
				{
					this.MoveAcceleration = MathHelper.Clamp(this.MoveAcceleration + this.Acceleration,
						-maxAcceleration,
						maxAcceleration);
				}
			}
			else if (this.Movement < 0)
			{
				if (this.MoveAcceleration > -maxAcceleration)
				{
					this.MoveAcceleration = MathHelper.Clamp(this.MoveAcceleration - this.Acceleration,
						-maxAcceleration,
						maxAcceleration);
				}
			}
			else
			{
				if (this.MoveAcceleration > 0)
				{
					this.MoveAcceleration = MathHelper.Max(0, this.MoveAcceleration - this.Deceleration);
				}
				else if (this.MoveAcceleration < 0)
				{
					this.MoveAcceleration = MathHelper.Min(this.MoveAcceleration + this.Deceleration, 0);
				}
			}

			this.velocity.X += this.MoveAcceleration * elapsed;
			this.velocity.Y = MathHelper.Clamp(this.velocity.Y + this.GravityAcceleration * elapsed,
				-this.MaxFallSpeed,
				this.MaxFallSpeed);

			this.velocity.Y = this.DoJump(this.velocity.Y, gameTime);

			// 擬似ドラッグを水平方向に
			// 適用します。
			if (this.IsOnGround)
			{
				this.velocity.X *= this.GroundDragFactor;
			}
			else
			{
				this.velocity.X *= this.AirDragFactor;
			}

			// プレイヤーがその最高速度より速く走らないようにします。
			//this.velocity.X = MathHelper.Clamp(this.velocity.X, -MaxMoveSpeed, MaxMoveSpeed);

			// 速度を適用します。
			this.Position += this.velocity * elapsed;
			this.Position = new Vector2((float) Math.Round(this.Position.X),
				(float) Math.Ceiling(this.Position.Y));

			// 現在プレイヤーがレベルと衝突している場合、これらを分離します。
			this.HandleCollisions();

			// 衝突によって移動が停止した場合、速度をゼロにリセットします。
			if (this.Position.X == previousPosition.X)
			{
				this.velocity.X = 0;
			}

			if (this.Position.Y == previousPosition.Y)
			{
				this.velocity.Y = 0;
			}
		}

		/// <summary>
		///     ジャンプに対応する Y 速度を計算し、それに応じて
		///     アニメーション表示します。
		/// </summary>
		/// <remarks>
		///     ジャンプが強勢されている間、Y 速度は完全に出力曲線によって
		///     オーバーライドされます。ジャンプが穏やかな間、重力が支配します。
		///     ジャンプの速度は、現在のジャンプを強勢する時間を測定する
		///     jumpTime フィールドによって制御されます。
		/// </remarks>
		/// <param name="velocityY">
		///     プレイヤーの Y 軸に沿った現在の速度。
		/// </param>
		/// <returns>
		///     ジャンプを開始または続行する場合、新しい Y 速度。
		///     それ以外の場合、既存の Y 速度。
		/// </returns>
		private float DoJump(float velocityY, GameTime gameTime)
		{
			// プレイヤーがジャンプしたい場合
			if (this.IsJumping)
			{
				// ジャンプを開始または続行する
				if ((!this.wasJumping && (this.IsOnGround || this.ForceJump)) || this.jumpTime > 0.0f)
				{
					if (this.jumpTime == 0.0f)
					{
						this.OnStartJump();
					}

					this.jumpTime += (float) gameTime.ElapsedGameTime.TotalSeconds;

				}

				// ジャンプが強勢されている場合
				if (0.0f < this.jumpTime && this.jumpTime <= this.MaxJumpTime)
				{
					// プレイヤーがジャンプの最高点をさらに制御できるように、
					// 出力曲線によって垂直方向の速度を完全にオーバーライドします。
					velocityY = this.JumpLaunchVelocity *
					            (1.0f - (float) Math.Pow(this.jumpTime / this.MaxJumpTime, this.JumpControlPower));
				}
				else
				{
					// ジャンプの最高点に到達した
					this.jumpTime = 0.0f;
				}
			}
			else
			{
				// ジャンプなしを続けるか、進行中のジャンプを取り消す
				this.jumpTime = 0.0f;
			}
			this.wasJumping = this.IsJumping;

			return velocityY;
		}

		public bool ForceJump { get; set; }

		protected virtual void OnStartJump()
		{
			if (this.StartJump != null)
			{
				this.StartJump(this,new EventArgs());
			}
		}

		public event EventHandler StartJump;

		/// <summary>
		///     プレイヤーと隣接するタイルとのすべての衝突を検出し、解決します。
		///     衝突が検出された場合、重なり合いを防ぐために、プレイヤーは
		///     1 つの軸に沿って押し出されます。移動の方向によって異なる動作を行うプラット
		///     フォームを処理するために、Y 軸に対するいくつかの特殊なロジックがあります。
		/// </summary>
		private void HandleCollisions()
		{
			if (this.DisableCollision)
			{
				return;
			}

			// プレイヤーの境界矩形を取得し、隣接するタイルを検出します。
			Rectangle bounds = this.BoundingRectangle;
			var leftTile = (int) Math.Floor((float) bounds.Left / Tile.Width);
			int rightTile = (int) Math.Ceiling(((float) bounds.Right / Tile.Width)) - 1;
			var topTile = (int) Math.Floor((float) bounds.Top / Tile.Height);
			int bottomTile = (int) Math.Ceiling(((float) bounds.Bottom / Tile.Height)) - 1;

			// フラグをリセットして、地面との衝突を検索します。
			this.isOnGround = false;
			// 潜在的に衝突する可能性のある
			// 各ファイルに対して繰り返します。
			for (int y = topTile; y <= bottomTile; ++y)
			{
				for (int x = leftTile; x <= rightTile; ++x)
				{
					// このタイルが衝突可能な場合、
					TileCollision collision = this.Stage.GetCollision(x, y);
					collision = this.GetCollision(collision, bounds);
					if (collision != TileCollision.Passable)
					{
						// 衝突の深さ (方向を含む) と強さを決定します。
						Rectangle tileBounds = this.Stage.GetBounds(x, y);
						Vector2 depth = bounds.GetIntersectionDepth(tileBounds);
						if (depth != Vector2.Zero)
						{
							float absDepthX = Math.Abs(depth.X);
							float absDepthY = Math.Abs(depth.Y);

							var newPosition = new Vector2(this.Position.X, this.Position.Y);
							if (this.previousRight <= tileBounds.Left &&
							    this.previousTop >= tileBounds.Bottom)
							{
								Console.WriteLine("Bump" + depth.X);
							}
							// 浅い軸に沿って衝突を解決します。
							if ((absDepthY < absDepthX)
							    || collision == TileCollision.Platform
							    || this.previousBottom <= tileBounds.Top)
							{
								// タイルの上部で交差する場合、地面上にいます。
								if (this.previousBottom <= tileBounds.Top)
								{
									this.isOnGround = true;
								}

								if (this.previousTop > tileBounds.Bottom)
								{
									this.jumpTime = 0.0f;

									if (this is Player)
									{
										Tile tile = this.stage.Tiles[x, y];
										if (x == leftTile)
										{
											Rectangle rightBounds = this.Stage.GetBounds(x + 1, y);
											Vector2 rightDepth = bounds.GetIntersectionDepth(rightBounds);
											float rightAbsDepthX = Math.Abs(rightDepth.X);

											if (rightAbsDepthX > absDepthX)
											{
												tile = this.stage.Tiles[x + 1, y];
												
											}
										}
										
										this.OnBump(tile);
									}
								}

								// 地面上にいない限り、プラットフォームを無視します。
								if (collision == TileCollision.Impassable
								    || collision == TileCollision.Breakable || this.IsOnGround)
								{
									// Y 軸に沿って衝突を解決します。
									this.Position = new Vector2(newPosition.X, newPosition.Y + depth.Y);

									// 新しい境界を使用して、さらに衝突を実行します。
									bounds = this.BoundingRectangle;
								}
							}
							else if (collision == TileCollision.Impassable
							         || collision == TileCollision.Breakable) // プラットフォームを
								// 無視します。
							{
								// X 軸に沿って衝突を解決します。
								this.Position = new Vector2(newPosition.X + depth.X, newPosition.Y);

								// 新しい境界を使用して、さらに衝突を実行します。
								this.MoveAcceleration = 0;
							}
							bounds = this.BoundingRectangle;
							//Console.WriteLine(depth.X);
						}
					}
				}
			}
			OnMoved();
			// 新しい境界の底面を保存します。
			this.previousBottom = bounds.Bottom;
			this.previousTop = bounds.Top;
			this.previousLeft = bounds.Left;
			this.previousRight = bounds.Right;
		}

		protected virtual void OnMoved()
		{
			
		}

		protected virtual TileCollision GetCollision(TileCollision collision,Rectangle bounds)
		{
			return collision;
		}

		private void OnBump(Tile tile)
		{
			if (this.Bumped != null)
			{
				this.Bumped(this, new BumpedEventArgs(tile));
			}
		}

		public event EventHandler<BumpedEventArgs> Bumped;
		public class BumpedEventArgs : EventArgs
		{
			public BumpedEventArgs(Tile tile)
			{
				this.Tile = tile;
			}
			internal Tile Tile { get; set; }
		}

		/// <summary>
		///     プレイヤーが倒されたときに呼び出されます。
		/// </summary>
		/// <param name="killedBy">
		///     プレイヤーを倒した敵。プレイヤーが敵に倒されなかった場合 (穴に落ちた場合)、
		///     このパラメーターは null です。
		/// </param>
		public virtual void OnDamaged(Character killedBy)
		{
			
		}

		public virtual void OnKilled(Character killedBy)
		{
			this.velocity = Vector2.Zero;
			this.MoveAcceleration = 0;

			if (Killed != null)
			{
				this.Killed(this,new EventArgs());
			}
		}

		public event EventHandler Killed;

		/// <summary>
		///     アニメーション表示された
		///     プレイヤーを描画します。
		/// </summary>
		public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
		{
			if (!this.Appearance)
			{
				return;
			}

			previousSwich = !previousSwich;
			if (Switch && !previousSwich)
			{
				return;
			}

			if (this.AllowTurnImage)
			{
				if (this.Direction == FaceDirection.Right)
				{
					this.flip = SpriteEffects.FlipHorizontally;
				}
				else
				{
					this.flip = SpriteEffects.None;
				}
			}
			if (this.FlipVerTically)
			{
				flip = flip | SpriteEffects.FlipVertically;
				
			}

			// このスプライトを
			// 描画します。
			var displayPosition = new Vector2(this.Position.X - this.stage.StagePosision + Shift.X, this.Position.Y + Shift.Y);
			this.Sprite.Draw(gameTime, spriteBatch, displayPosition, this.flip);
		}

		public bool FlipVerTically { get; set; }

		public bool DisableCollision { get; set; }
	}

}
